package cc.magicjson.easy.batch.strategy;

import cc.magicjson.easy.batch.core.BatchStrategy;
import cc.magicjson.easy.batch.exception.BatchOperationException;
import cc.magicjson.easy.batch.model.BatchContext;
import cc.magicjson.easy.batch.model.BatchResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.util.CollectionUtils;

import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.List;

/**
 * Simple batch processing strategy without transaction support.
 * Handles basic batch operations with error handling capabilities.
 */
@Slf4j
public class SimpleBatchStrategy<T> implements BatchStrategy<T> {
    @Override
    public BatchResult<T> execute(BatchContext<T> context) {
        List<T> items = context.getItems();
        if (CollectionUtils.isEmpty(items)) {
            return new BatchResult<>();
        }

        BatchResult<T> result = new BatchResult<>();
        String sql = context.getSqlProvider().getSql(context.getSqlKey());
        result.setTotalItems(items.size());

        try {
            int[] updateCounts = executeBatchUpdate(context, sql);
            updateBatchResult(result, updateCounts);
        } catch (Exception e) {
            handleError(result, e, context.isAllowPartialSuccess());
        }

        return result;
    }

    private int[] executeBatchUpdate(BatchContext<T> context, String sql) {
        return context.getJdbcTemplate().batchUpdate(
            sql,
            new BatchPreparedStatementSetter() {
                @Override
                public void setValues(PreparedStatement ps, int i) throws SQLException {
                    Object[] params = context.getParameterExtractor()
                        .extractParameters(context.getItems().get(i));
                    setParameters(ps, params);
                }

                @Override
                public int getBatchSize() {
                    return context.getItems().size();
                }
            });
    }

    private void setParameters(PreparedStatement ps, Object[] params) throws SQLException {
        for (int j = 0; j < params.length; j++) {
            ps.setObject(j + 1, params[j]);
        }
    }

    private void updateBatchResult(BatchResult<T> result, int[] updateCounts) {
        result.setAffectedRows(Arrays.stream(updateCounts).sum());
        result.setSuccessful(true);
    }

    protected void handleError(BatchResult<T> result, Exception e, boolean allowPartialSuccess) {
        result.setSuccessful(false);
        result.setErrorMessage(e.getMessage());
        if (!allowPartialSuccess) {
            throw new BatchOperationException("Batch operation failed", e);
        }
    }
}
